package com.bkck.sharkblockade.levels.thread;

import com.bkck.sharkblockade.highscores.ScoresDB;
import com.bkck.sharkblockade.levels.block.Block;
import com.bkck.sharkblockade.levels.level.Level;
import com.bkck.sharkblockade.levels.level1.LevelOne;
import com.bkck.sharkblockade.levels.level2.LevelTwo;
import com.bkck.sharkblockade.levels.level3.LevelThree;
import com.bkck.sharkblockade.levels.level4.LevelFour;
import com.bkck.sharkblockade.levels.level5.LevelFive;
import android.graphics.Canvas;
import android.os.Message;
import android.util.Log;
import android.view.SurfaceHolder;

/**
 * The main thread which contains the game loop.
 * 
 * @author Team BKCK
 * 
 */
public class MainThread extends Thread {

  private static final String TAG = MainThread.class.getSimpleName();

  // max frames per second
  private final static int MAX_FPS = 40;
  private final static int MAX_FRAME_SKIPS = 5;
  private final static int FRAME_INTERVAL = 1000 / MAX_FPS;

  private int totalFrames;

  // surface holder that can access the physical surface
  private SurfaceHolder surfaceHolder;
  // The view that handles inputs and draws to the surface
  private MainGamePanel gamePanel;

  // to calculate for total elapsed time
  private long startTime;
  private long currentTime;

  private boolean running = false;

  private static int lives;
  private static int score;

  // Scores Database
  private static ScoresDB db;

  /**
   * Constructor.
   * 
   * @param surfaceHolder
   * @param gamePanel
   */
  public MainThread(SurfaceHolder surfaceHolder, MainGamePanel gamePanel) {
    super();
    this.surfaceHolder = surfaceHolder;
    this.gamePanel = gamePanel;
    // set initial lives and score
    lives = 5;
    score = 0;
  }

  /**
   * Sets and creates the DB for holding scores.
   * 
   * @param createdDB The DB to set.
   */
  public static void setDB(ScoresDB createdDB) {
    db = createdDB;
  }

  /**
   * Accessor method for scores database.
   * 
   * @return The scores database
   */
  public static ScoresDB getDB() {
    return db;
  }

  /**
   * Set whether to let this thread run.
   * 
   * @param running
   */
  public void setRunning(boolean running) {
    this.running = running;
  }

  /**
   * Returns whether this thread is running.
   * 
   * @return
   */
  public boolean isRunning() {
    return this.running;
  }

  /**
   * Returns the elapsed time since the start of this game.
   * 
   * @return
   */
  public long getTimeElapsed() {
    return currentTime - startTime;
  }

  /**
   * Set the start time.
   * 
   * @param startTime The start time.
   */
  public void setStartTime(long startTime) {
    this.startTime = startTime;
  }

  /**
   * Return the start time.
   * 
   * @return The start time.
   */
  public long getStartTime() {
    return startTime;
  }

  /**
   * Run the thread.
   */
  @Override
  public void run() {
    Canvas canvas;

    // variables needed to achieve constant fps
    long cycleBeginTime = 0;
    long timeDiff = 0;
    int sleepTime = 0;
    int framesSkipped = 0;

    totalFrames = 0;

    while (running) {

      // update current time
      currentTime = System.currentTimeMillis();

      // update TextViews components in this level if the game is still going on
      updateLevelComponents();

      // end thread if the player won
      if (Block.blocksToBreak == 0) {
        Message msg = new Message();
        msg.what = Level.WIN_IDENTIFIER;
        Level.handler.sendMessage(msg);
        return;
      }

      // end thread if the player lost
      if (lives == 0) {
        Message msg = new Message();
        msg.what = Level.LOSE_IDENTIFIER;
        Level.handler.sendMessage(msg);
        return;
      }

      // update the images
      canvas = null;
      try {
        canvas = this.surfaceHolder.lockCanvas();
        synchronized (surfaceHolder) {

          cycleBeginTime = System.currentTimeMillis();
          framesSkipped = 0;

          this.gamePanel.update();
          this.gamePanel.render(canvas);
          totalFrames++;

          timeDiff = System.currentTimeMillis() - cycleBeginTime;
          sleepTime = (int) (FRAME_INTERVAL - timeDiff);
        }
        // if the game updated and rendered this cycle faster than expected
        if (sleepTime > 0) {
          // put the thread to sleep for a short time to save battery.
          try {
            Thread.sleep(sleepTime);
          }
          catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
        // catch up on update without rendering if there's lag
        while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) {
          this.gamePanel.update();
          sleepTime += FRAME_INTERVAL;
          framesSkipped++;
        }
        // check the fps
        // Did (totalFrames % 200) so it only prints out once a while to avoid slowing down
        // the real speed (The difference was huge!!)
        if (getTimeElapsed() >= 1000 && totalFrames % 200 == 0) {
          Log.d(TAG, "FPS: " + (totalFrames / (getTimeElapsed() / 1000)));
        }
      }
      // do it in final so it's always executed
      finally {
        if (canvas != null) {
          surfaceHolder.unlockCanvasAndPost(canvas);
        }
      }
    }
  }

  /**
   * Called when a ball was eaten by a shark or went below the paddle.
   */
  public static void loseOneLife() {
    lives--;
  }

  /**
   * Returns the current lives left.
   * 
   * @return
   */
  public static int getLives() {
    return lives;
  }

  /**
   * Set current lives.
   * 
   * @param newlives
   */
  public static void setLives(int newlives) {
    lives = newlives;
  }

  /**
   * Returns the current score.
   * 
   * @return
   */
  public static int getScore() {
    return score;
  }

  /**
   * Add the amount of points passed in to the current score.
   * 
   * @param amount
   */
  public static void addScore(int amount) {
    score += amount;
  }

  /**
   * Set the score.
   * 
   * @param newScore The score.
   */
  public static void setScore(int newScore) {
    score = newScore;
  }

  /**
   * update the level TextView components from this thread.
   */
  private void updateLevelComponents() {

    Message msg = new Message();

    // update the TextView for time
    msg.what = Level.TIMER_IDENTIFIER;
    msg.obj = getTimeElapsed();
    Level.handler.sendMessage(msg);
    // update the TextView for lives
    msg = new Message();
    msg.what = Level.LIVES_IDENTIFIER;
    msg.obj = lives;
    Level.handler.sendMessage(msg);
    // update the TextView for score
    msg = new Message();
    msg.what = Level.SCORE_IDENTIFIER;
    msg.obj = score;
    Level.handler.sendMessage(msg);
  }
}
